/*-+-*****************************************************************************
 Copyright (C) 2009-2012 Oceanside Software Corporation (R)
 Dallas, Texas

 Author       : Jason T. Brower
 Purpose      : This file is part of a point-of-sale software package
                originally written for Table-Service Restaurants.
              
 Date         : July 8th of 2012
              
 History      : The application that this software file belongs to was 
                written over a 3-Year period.  The first version was written
                in C# .Net 3.5 using Windows Forms.  Being new to .Net at
                the time, I soon learned of Windows Presentation Foundation,
                liked the graphics capabilities and databinding so I
                migrated to WPF.  When the .Net 4.0 framework was released
                the application was migrated from 3.5 to 4.0. Now today,
                .Net 4.5 RC is out and I was already in process of migrating
                before I made this public.
                
Excuse Me     : Please excuse the coding style, it is not representative of
                my typical work.  During the three years I was under a very
                tight budget, constantly running out of funding and was 
                working 18 hour days hyped up on 5-Hour Energy.  The 
                design is not textbook.  It is monolythic because of
                previous issues I will not get into.  There are few comments
                in the code and the ones that are there may be incorrect.
                
What it is not: This is not the exact version that I took through PA-DSS 2.0
                nor is it the exact version running at my customer sites.
                I removed some details for security purposes including some
                encryption algorithms.  The reporting was removed because
                I was including a third party library that cannot be 
                published with this Open Source.
                
What it is    : This application has been running at high volume restaraunts
                for over a year.  It never freezes, crashes or does anything
                unexpected.  So long as it is built, installed and configured
                correctly, it is a strong system that we can all enhance.

Contact       : Please note that I can no longer work for free.  However, you
                can reach me at information@oceansidesw.com.                                                      

*********************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
#if _LOCALDB
  using Oceanside.LocalDB;
#else
using Oceanside.RemoteDB;
#endif

#if _LOCALDB
  namespace Oceanside.LocalDB
#else
namespace Oceanside.RemoteDB
#endif
{
    public partial class ORDER : INotifyPropertyChanged
    {
        public const decimal ROUND_ERROR = .009M;


        public ORDER()
        {

        }
        public ORDER(int orderNumber, USER user, bool isbarseat = false, int seatcount = 1)
        {
            KIOSK k = KIOSK.Get(SHIFT.GlobalContext);
            this.IsVisible = Visibility.Visible;
            this.IsBarSeat = isbarseat;
            this.CustomerName = null;
            this.HasHeadOfTable = false;
            this.TableInt = orderNumber;
            this.UserId = user.Id;
            this.KioskWhereCreatedId = k.Id;
            this.RevenueCenterId = k.RevenueCenterId;
            this.GuestCount = seatcount;
            this.TableName = orderNumber.ToString();
            this.OpenDateStamp = DateTime.Now;
            this.LastModifiedDateStamp = DateTime.Now;
            this.LastAttentedToDateStamp = DateTime.Now;
            this.CreatorId = user.Id;
            this.BusinessDayId = BUSINESS_DAY.GetNow(SHIFT.GlobalContext).Id;
        }

        [field: NonSerialized]
        public new event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            try
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, e);
                }
            }
            catch (Exception excep)
            {
                Constants.ErrorDispatcher.Notify(Error.EType.E_PLACEHOLDER, excep);
            }
        }
        public Visibility IsVisible
        {
            get;
            set;
        }

        public bool CanDeleteOrder()
        {
            bool canDelete = false;

            try
            {
                bool hasSentItems = SALES.Where(s => s.CountPendingKitchenSend != s.CountOrdered).Any();
                bool hasTransactions = TRANSACTIONS.Any();
                bool hasSoldGiftCards = GIFT_CARDS_SOLDS.Any();
                if (!hasSentItems && !hasTransactions && !hasSoldGiftCards)
                {
                    canDelete = true;
                }
            }
            catch (Exception ex)
            {
                Logging.LogException(ex);
            }

            return canDelete;
        }

        public class DiscountDisplay
        {
            string type;
            public string Type
            {
                get { return type; }
                set { type = value; }
            }

            string appliedTo;
            public string AppliedTo
            {
                get { return appliedTo; }
                set { appliedTo = value; }
            }

            string manager;
            public string Manager
            {
                get { return manager; }
                set { manager = value; }
            }

            decimal amount;
            public decimal Amount
            {
                get { return amount; }
                set { amount = value; }
            }

            Int64 discountId;
            public Int64 DiscountId
            {
                get { return discountId; }
                set { discountId = value; }
            }

            bool isItemLevelDiscount = false;
            public bool IsItemLevelDiscount
            {
                get { return isItemLevelDiscount; }
                set { isItemLevelDiscount = value; }
            }

            Int64 appliedToSaleId;
            public Int64 AppliedToSaleId
            {
                get { return appliedToSaleId; }
                set { appliedToSaleId = value; }
            }
        }

        public List<SALE> GetSeatsSales(int seatNo)
        {
            return SALES
                .Where(s => s.OwningSeatNumber == seatNo && !s.WasVoided && !s.WasCompedInEntire)
                .OrderBy(s => s.SeatSequenceNumber).ToList();
        }
        public List<SALE> GetChecksSales(int checkNo)
        {
            return SALES
                .Where(s => s.OwningCheckNumber == checkNo && !s.WasVoided && !s.WasCompedInEntire)
                .OrderBy(s => s.CheckSequenceNumber).ToList();
        }
        public List<DiscountDisplay> GetChecksDiscounts(ORDER order, int checkNo, DominicaEntities context)
        {
            List<DiscountDisplay> discounts = new List<DiscountDisplay>();
            IEnumerable<SALE> sales = order.SALES
                .Where(s => s.OwningCheckNumber == checkNo && s.DiscountId != null && s.DiscountId != 0);

            foreach (SALE s in sales)
            {
                COMP_ACCOUNT ca = context.COMP_ACCOUNTS.Where(a => a.Id == s.DiscountId).FirstOrDefault();
                if (ca != null)
                {
                    if (s.WasVoided)
                    {
                        DiscountDisplay dc = new DiscountDisplay();
                        dc.IsItemLevelDiscount = true;
                        dc.Type = ca.Name;
                        dc.AppliedToSaleId = s.Id;
                        if (s.ProductName != null && s.ProductName.Length > 13)
                        {
                            dc.AppliedTo = s.ProductName.Substring(0, 13);
                        }
                        else
                        {
                            dc.AppliedTo = s.ProductName;
                        }
                        dc.Manager = USER.GetUsernameById((long)s.DiscountManagerId);
                        dc.Amount = s.DiscountRate * s.ActualPrice * s.CountOrdered;
                        dc.DiscountId = (Int64)s.DiscountId;
                        discounts.Add(dc);
                    }
                    else if (s.WasCompedInEntire)
                    {
                        DiscountDisplay dc = new DiscountDisplay();
                        dc.IsItemLevelDiscount = true;
                        dc.Type = ca.Name;
                        dc.AppliedToSaleId = s.Id;
                        if (s.ProductName != null && s.ProductName.Length > 13)
                        {
                            dc.AppliedTo = s.ProductName.Substring(0, 13);
                        }
                        else
                        {
                            dc.AppliedTo = s.ProductName;
                        }
                        dc.Manager = USER.GetUsernameById((long)s.DiscountManagerId);
                        dc.Amount = s.DiscountRate * s.ActualPrice * s.CountOrdered;
                        dc.DiscountId = (Int64)s.DiscountId;
                        discounts.Add(dc);
                    }
                    else
                    {
                        if (ca != null)
                        {
                            DiscountDisplay dc = discounts.Where(d => d.DiscountId == ca.Id && !d.IsItemLevelDiscount).FirstOrDefault();
                            if (dc == null)
                            {
                                dc = new DiscountDisplay();
                            }
                            if (dc.Type == null)
                            {
                                if (s.DiscountAppliedAtCheckLevel)
                                {
                                    dc.AppliedTo = "Check";
                                }
                                else
                                {
                                    if (s.ProductName != null && s.ProductName.Length > 13)
                                    {
                                        dc.AppliedTo = s.ProductName.Substring(0, 13);
                                        dc.AppliedToSaleId = s.Id;
                                    }
                                    else
                                    {
                                        dc.AppliedTo = s.ProductName;
                                        dc.AppliedToSaleId = s.Id;
                                    }
                                }
                                dc.Type = ca.Name;
                                dc.IsItemLevelDiscount = !s.DiscountAppliedAtCheckLevel;
                                dc.Manager = USER.GetUsernameById((long)s.DiscountManagerId);
                                dc.Amount = s.DiscountRate * s.CountOrdered * s.ActualPrice;
                                dc.DiscountId = (Int64)s.DiscountId;
                                discounts.Add(dc);
                            }
                            else
                            {
                                dc.Amount += s.DiscountRate * s.CountOrdered * s.ActualPrice;
                            }
                        }
                    }
                }
            }
            return discounts;
        }
        public List<TRANSACTION> GetChecksTransactions(int checkNo)
        {
            try
            {
                return TRANSACTIONS.Where(s => s.OwningCheckNumber == checkNo).ToList();
            }
            catch (Exception ex)
            {
                Logging.LogException(ex);
                return null;
            }
        }
        public List<SALE> ChecksCompableSales(int checkno)
        {
            List<SALE> sales = null;
            try
            {
                sales = SALES.Where(s => s.ProductIsDiscountable && s.OwningCheckNumber ==
                    checkno && s.DiscountId == null && !s.IsGiftCardSale).ToList();
            }
            catch (Exception e)
            {
                Logging.LogException(e);
            }
            return sales;
        }

        public decimal RoundedChecksCompableSalesSubotal(int checkno)
        {
            decimal total = 0.0M;
            try
            {
                total = SALES
                    .Where(s => s.ProductIsDiscountable && s.OwningCheckNumber == checkno && s.DiscountId == null).Sum(s => s.ActualPrice);
            }
            catch (Exception e)
            {
                Logging.LogException(e);
            }
            return Math.Round(total, 2, MidpointRounding.AwayFromZero);
        }
        public static decimal RoundedSurchargePerSale(SALE s)
        {
            ///Get the total surcharge amount.
            decimal surchargePerSale = (s.SurchargeRate * s.ActualPrice) + s.SurchargeAmountFlatFee;
            return Math.Round(surchargePerSale, 2, MidpointRounding.AwayFromZero);
        }
        public static decimal RoundedAutoGratuityPerSale(SALE s)
        {
            ///Apply autogratuity to the non discounted rate.
            decimal priceForGratuity = s.ActualPrice;
            //decimal priceForGratuity = s.ActualPrice - (s.ActualPrice * s.DiscountRate);
            if (s.ApplyAutoGratToMenuPrice)
            {
                priceForGratuity = s.ActualPrice;
            }
            return Math.Round((s.AutoGratuityRate * priceForGratuity), 2, MidpointRounding.AwayFromZero);
        }

        public static decimal SalesGrandTotal(List<SALE> sales, bool deductDiscounts = true)
        {
            decimal sum = 0M;
            decimal dontCare = 0;
            foreach (SALE s in sales)
            {
                ///Get the total surcharge amount.
                decimal roundedSurchargePerSale = RoundedSurchargePerSale(s);

                ///Calculate the autogratuity per sale.
                decimal roundedAutoGratuityPerSale = RoundedAutoGratuityPerSale(s);

                ///Sum up the grand total of the check.
                sum += s.CountOrdered * (roundedSurchargePerSale + roundedAutoGratuityPerSale);

                if (deductDiscounts)
                {
                    ///Dont forget to add the cost
                    sum += s.CountOrdered * (s.ActualPrice - (s.ActualPrice * s.DiscountRate));
                }
                else
                {
                    sum += s.CountOrdered * s.ActualPrice;
                }
            }
            return Math.Round(sum, 2, MidpointRounding.AwayFromZero) + SalesExclusiveTaxTotal(sales, out dontCare);
        }

        public decimal SelCheckDiscounts
        {
            get
            {
                return SHIFT.SelectedOrder.GetChecksDiscounts(SHIFT.SelectedOrder,
                    CCBindings.SelectedSeatIndex, SHIFT.GlobalContext).Sum(d => d.Amount);
            }
        }
        public decimal SelCheckSurcharges
        {
            get
            {
                decimal sum = 0;
                IEnumerable<SALE> sales = GetChecksSales(CCBindings.SelectedSeatIndex);
                foreach (SALE s in sales)
                {
                    sum += s.CountOrdered * RoundedSurchargePerSale(s);
                }
                return sum;
            }
        }
        public decimal SelCheckTax
        {
            get
            {
                ///Grab the selected checks sales.
                List<SALE> sales = GetChecksSales(CCBindings.SelectedSeatIndex);
                decimal dontcare = 0;
                return SalesExclusiveTaxTotal(sales, out dontcare);
            }
        }

        public decimal SelCheckPaidSubtotal
        {
            get
            {
                ///No need to round because the amounts should already be nice numbers.
                return TRANSACTIONS.Where(t => t.OwningCheckNumber == CCBindings.SelectedSeatIndex
                    && !t.WasVoided).Sum(t => t.Base);
            }
        }
        public decimal SelCheckRoundedSubtotal
        {
            get
            {
                decimal amount = SALES
                    .Where(s => (s.OwningCheckNumber == CCBindings.SelectedSeatIndex) && !s.WasVoided && !s.WasCompedInEntire)
                    .Sum(s => s.CountOrdered * (s.ActualPrice - (s.DiscountRate * s.ActualPrice)));
                return Math.Round(amount, 2, MidpointRounding.AwayFromZero);
            }
        }
        public decimal SelCheckUnpaidSubtotal
        {
            get
            {
                return SelCheckRoundedSubtotal - SelCheckPaidSubtotal;
            }
        }
        public decimal SelCheckRoundedSubtotalWithDiscounts
        {
            get
            {
                decimal amount = SALES
                    .Where(s => (s.OwningCheckNumber == CCBindings.SelectedSeatIndex) && !s.WasVoided && !s.WasCompedInEntire)
                    .Sum(s => s.CountOrdered * s.ActualPrice);
                return Math.Round(amount, 2, MidpointRounding.AwayFromZero);
            }
        }
        public decimal SelCheckGrandTotal
        {
            get
            {
                List<SALE> sales = GetChecksSales(CCBindings.SelectedSeatIndex);
                return SalesGrandTotal(sales);
            }
        }
        public decimal SelCheckPaidTotalWithoutGratuity
        {
            get
            {
                return TRANSACTIONS
                    .Where(t => t.OwningCheckNumber == CCBindings.SelectedSeatIndex && !t.WasVoided)
                    .Sum(t => t.Base);
            }
        }
        public decimal SelCheckAutoGratutiy
        {
            get
            {
                decimal sum = 0M;
                IEnumerable<SALE> sales = GetChecksSales(CCBindings.SelectedSeatIndex);
                foreach (SALE s in sales)
                {
                    sum += (s.CountOrdered * RoundedAutoGratuityPerSale(s));
                }
                return sum;
            }
        }
        public decimal SelCheckUnpaidGrandTotal
        {
            get
            {
                return SelCheckGrandTotal - SelCheckPaidTotalWithoutGratuity;
            }
        }
        public static decimal ExclusiveTaxForSale(SALE s)
        {
            decimal taxableAmount = 0;
            taxableAmount += RoundedSurchargePerSale(s);
            taxableAmount += s.ActualPrice - (s.ActualPrice * s.DiscountRate);
            return Math.Round(taxableAmount * s.ExclusiveTaxRate, 2, MidpointRounding.AwayFromZero);
        }


        public static decimal SalesExclusiveTaxTotal(List<SALE> sales)
        {
            decimal dontCare;
            return SalesExclusiveTaxTotal(sales, out dontCare);
        }
        public static decimal SalesExclusiveTaxTotal(List<SALE> sales, out decimal taxableAmount)
        {
            decimal sum = 0;
            taxableAmount = 0M;
            try
            {
                if (sales != null && sales.Count != 0)
                {
                    ///In order to calculate the tax on the check, we need to group every
                    ///product by its tax rates.  The apply the rates that way.  If we
                    ///do it on a sale by sale basis we will end up with some bigtime
                    ///rounding errors.

                    ///Grab all unique exlusive tax rates (almost always one sales tax rate)
                    IEnumerable<decimal> taxRates = sales.Where(s => s.ExclusiveTaxRateId != null && !s.WasVoided && !s.WasCompedInEntire)
                        .Select(s => s.ExclusiveTaxRate).Distinct();

                    List<decimal> eachTaxrateTotal = new List<decimal>(taxRates.Count());
                    foreach (decimal taxForLikeTaxRates in taxRates)
                    {
                        List<SALE> salesAtTaxRate = sales.Where(s => s.ExclusiveTaxRate == taxForLikeTaxRates && !s.WasVoided && !s.WasCompedInEntire).ToList();
                        decimal taxableAmountForLikeTaxRates = 0M;

                        foreach (SALE s in salesAtTaxRate)
                        {
                            taxableAmountForLikeTaxRates += RoundedSurchargePerSale(s);
                            taxableAmountForLikeTaxRates += s.CountOrdered * (s.ActualPrice - (s.ActualPrice * s.DiscountRate));
                        }
                        decimal taxSubtotalForLikeTaxRates = taxableAmountForLikeTaxRates * taxForLikeTaxRates;
                        if (taxSubtotalForLikeTaxRates != 0)
                        {
                            taxableAmount += taxableAmountForLikeTaxRates;
                            eachTaxrateTotal.Add(taxSubtotalForLikeTaxRates);
                        }
                    }

                    sum = eachTaxrateTotal.Sum();
                    sum = Math.Round(sum, 2, MidpointRounding.AwayFromZero);
                    taxableAmount = Math.Round(taxableAmount, 2, MidpointRounding.AwayFromZero);
                }
            }
            catch (Exception e)
            {
                Logging.LogException(e);
            }
            return sum;
        }
        public static decimal SalesInclusiveTaxTotal(List<SALE> sales, DominicaEntities context, out decimal salesTotal)
        {
            decimal sum = 0;
            salesTotal = 0M;
            try
            {
                salesTotal = sales.Sum(s => (s.CountOrdered * (s.ActualPrice - (s.DiscountRate * s.ActualPrice))));
                sum = sales.Sum(s => (s.CountOrdered * (s.ActualPrice - (s.DiscountRate * s.ActualPrice))) - ((s.CountOrdered * (s.ActualPrice - (s.DiscountRate * s.ActualPrice))) / (s.InclusiveTaxRate + 1)));

            }
            catch (Exception e)
            {
                Logging.LogException(e);
            }
            return sum;
        }


        public decimal RoundedSalesSubtotal
        {
            get
            {
                decimal amount = SALES
                    .Where(s => !s.WasVoided && !s.WasCompedInEntire)
                    .Sum(s => s.CountOrdered * (s.ActualPrice - (s.DiscountRate * s.ActualPrice)));
                return Math.Round(amount, 2, MidpointRounding.AwayFromZero);
            }
        }


        public decimal ChecksGrandTotal(int checkNo)
        {
            List<SALE> sales = GetChecksSales(checkNo);
            return SalesGrandTotal(sales);
        }



        public decimal TableGrandTotal()
        {
            return SalesGrandTotal(SHIFT.SelectedOrder.SALES.Where(s => !s.WasCompedInEntire && !s.WasVoided).ToList());
        }

        public decimal EntireTableGrandTotal
        {
            get
            {
                return SalesGrandTotal(SHIFT.SelectedOrder.SALES.Where(s => !s.WasCompedInEntire && !s.WasVoided).ToList());
            }
        }
        public decimal SpecifiedSeatBarSubtotal(int seatNo)
        {
            decimal amount = SALES
                .Where(s => s.OwningSeatNumber == seatNo && !s.WasVoided && !s.WasCompedInEntire)
                .Sum(s => s.CountOrdered * (s.ActualPrice - (s.DiscountRate * s.ActualPrice)));
            return Math.Round(amount, 2, MidpointRounding.AwayFromZero);
        }
        public decimal CheckPaidTotalWithoutGratuity(int checkNo)
        {
            return TRANSACTIONS
                .Where(t => t.OwningCheckNumber == checkNo && !t.WasVoided)
                .Sum(t => t.Base);
        }

        public decimal EntireTablePaidTotalWithoutGratuity
        {
            get
            {
                return TRANSACTIONS
                    .Where(t => !t.WasVoided)
                    .Sum(t => t.Base);
            }
        }

        public decimal CheckUnpaidGrandTotal(int check)
        {
            return ChecksGrandTotal(check) - CheckPaidTotalWithoutGratuity(check);
        }

        public decimal EntireTableUnpaidGrandTotal
        {
            get
            {
                return EntireTableGrandTotal - EntireTablePaidTotalWithoutGratuity;
            }
        }
        public decimal GetChecksAutoGratutiy(int checkNo)
        {
            decimal sum = 0M;
            IEnumerable<SALE> sales = GetChecksSales(checkNo);
            foreach (SALE s in sales)
            {
                sum += (s.CountOrdered * RoundedAutoGratuityPerSale(s));
            }
            return sum;
        }
        public bool CanCloseOrder
        {
            get
            {
                for (int i = 0; i < CheckCount; i++)
                {
                    if (!CanCloseSpecificCheck(i))
                    {
                        return false;
                    }
                }
                return true;
            }
        }
        public bool CanCloseSpecificCheck(int checkNo)
        {
            decimal bal = CheckUnpaidGrandTotal(checkNo);
            return bal <= ROUND_ERROR;
        }
        public bool CanCloseCheck
        {
            get
            {
                return SelCheckUnpaidGrandTotal <= ROUND_ERROR;
            }
        }
        public bool HasProductsThatNeedSent()
        {
            return SALES.Where(s => s.CountPendingKitchenSend > 0).Any();
        }
        public string TableNumber
        {
            get { return TableInt.ToString(); }
        }
        public string TableString
        {
            get
            {
                if (Constants.IsQuickServiceMode)
                {
                    return "Order " + TableInt;
                }
                else if (IsBarSeat)
                {
                    return "Bar #" + TableInt;
                }
                else
                {
                    return "Table #" + TableInt;
                }
            }
        }

        public decimal EqualPayAmountDue()
        {
            decimal unpaid = 0;
            if (SHIFT.SelectedOrder.EqualPayIsActive == true)
            {
                int count = (int)SHIFT.SelectedOrder.EqualPayCount;
                ///Split the amounts
                decimal partialRoundedAutoGratuity = SHIFT.SelectedOrder.SelCheckAutoGratutiy / count;
                decimal partialRoundedSubtotal = SHIFT.SelectedOrder.SelCheckRoundedSubtotal / count;
                decimal partialRoundedTax = SHIFT.SelectedOrder.SelCheckTax / count;

                ///Round updward
                partialRoundedAutoGratuity = Math.Round(partialRoundedAutoGratuity, 2, MidpointRounding.AwayFromZero);
                partialRoundedSubtotal = Math.Round(partialRoundedSubtotal, 2, MidpointRounding.AwayFromZero);
                partialRoundedTax = Math.Round(partialRoundedTax, 2, MidpointRounding.AwayFromZero);

                ///Calculate the error and adjust it on the last payment if this is the last payment.
                if (SHIFT.SelectedOrder.EqualPayCount == SHIFT.SelectedOrder.TRANSACTIONS.Count - 1)
                {
                    decimal autoGratuityError =
                        SHIFT.SelectedOrder.SelCheckAutoGratutiy - (partialRoundedAutoGratuity * count);

                    decimal subtotalError =
                        SHIFT.SelectedOrder.SelCheckRoundedSubtotal - (partialRoundedSubtotal * count);

                    decimal taxError =
                        SHIFT.SelectedOrder.SelCheckTax - (partialRoundedTax * count);

                    unpaid = (partialRoundedAutoGratuity + partialRoundedSubtotal + partialRoundedTax +
                        autoGratuityError + subtotalError + taxError);
                }
                else
                {
                    unpaid = (partialRoundedAutoGratuity + partialRoundedSubtotal + partialRoundedTax);
                }

                if (unpaid > SHIFT.SelectedOrder.SelCheckUnpaidGrandTotal)
                {
                    unpaid = SHIFT.SelectedOrder.SelCheckUnpaidGrandTotal;
                }
            }
            else
            {
                unpaid = SHIFT.SelectedOrder.SelCheckUnpaidGrandTotal;
            }
            return unpaid;
        }
    }
}

